// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ygen

import (
	"bytes"
	"fmt"
	"sort"
	"strings"
	"text/template"

	log "github.com/golang/glog"
	"github.com/google/go-cmp/cmp"

	"github.com/openconfig/gnmi/errlist"
	gpb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/goyang/pkg/yang"
	"github.com/openconfig/ygot/genutil"
	"github.com/openconfig/ygot/util"
	"github.com/openconfig/ygot/ygot"
)

const (
	// defaultSchemaVarName is the default variable name that should
	// be used for the output JSON schema when stored. It can be overridden
	// by specifying a name within the GoOptions field of the YANGCodeGenerator
	// instance used for code generation.
	defaultSchemaVarName string = "ySchema"
	// defaultPackageName specifies the default name that should be
	// used for the generated Go package.
	defaultPackageName string = "ocstructs"
	// DefaultAnnotationPrefix is the default string that is used to prefix the name
	// of metadata fields in the output Go structs.
	DefaultAnnotationPrefix string = "Λ"
	// annotationFieldType defines the type that should be used for the
	// annotation/metadata fields within each struct when they are generated.
	annotationFieldType string = "[]ygot.Annotation"
)

// The methods in this file take the structs that have been generated by
// processing the goyang output, and generate the Go code snippets that
// will be output to the caller of the library.
//
// These functions break down into two different sets:
//	1) Those that generate output for containers or lists, which are both
//	   Go structs.
//	2) Those that generate output for enumerated entities.
//
// For structs, some additional output is also generated. For example, if a struct
// storing the characteristics of a YANG container A is input, then the resulting
// GoStructCodeSnippet struct will contain the definition of the Go struct
// used to create an instance of A in the structDef string. The listKeys string
// contains any structs that are used as the key to a multi-key list. The methods
// string contains any functions that are generated with A as the receiver.
//
// As a worked example, if we have the following YANG input:
//
//	container A {
//		list B {
//			key "one two";
//			leaf one { type string; }
//			leaf two { type int8; }
//		}
//	}
//
// The resulting generated Go code consists of:
//
//	type A struct {
//		B map[A_B_Key]*A_B
//	}
//
// Where A_B is the struct that will be created for the list A/B, and A_B_Key is
// a struct created specifically to key the map for list B, based on it having
// multiple keys.
//
// The functions generated provide a method to create a new instance of B, or
// append an A_B struct to the map B whilst populating the relevant key fields
// of the list, or autocreating the key struct from the fields of the input
// structure. i.e., they are of the form:
//
// func (*A) NewB(string, int8) (*A_B, error) {
//	// Create a new instance of B, and its key, and append it to the map
//	// field of A.
// }
//
// func (*A) AppendB(*A_B) error {
//	// Append the instance of B that was handed to the function to the map
//	// field of A.
// }
//
// Each enumerated node within the YANG schema (an identity, enumeration leaf
// or typedef containin an enumeration) is output to a Go enumeration. This is
// formed by defining a new type, based on int64 - with constants defined for
// the values of the enumeration. Value 0 is always assigned to be UNSET, such
// that it is possible to determine that the enumerated value was not modified,
// with each subsequent value being assigned the next numeric value.
//
// If the name of an enumerated value is not Go safe - e.g., VALUE-ONE - it is
// converted to a Go-safe name using safeGoEnumeratedName, this function
// replaces characters that cannot be used within Go identifiers with
// underscores (where such characters are allowed in YANG identifiers).
//
// For example, a YANG identity defined in module "test-module" as:
//	identity BASE-IDENTITY;
//	identity VALUE-ONE { base "BASE-IDENTITY"; }
//	identity VALUE-TWO { base "BASE-IDENTITY"; }
//	identity VALUE-THREE { base "BASE-IDENTITY"; }
//
// is output to the following code:
//
//	type TestModule_BaseIdentity int64
//	const (
//		TestModule_BaseIdentity_UNSET = 0
//		TestModule_BaseIdentity_VALUE_ONE = 1
//		TestModule_BaseIdentity_VALUE_TWO = 2
//		TestModule_BaseIdentity_VALUE_THREE = 3
//	)
//
// Calling code can then set any identityref with a base of BASE-IDENTITY
// by setting a value to one of these constants.

// GoStructCodeSnippet is used to store the generated code snippets associated with
// a particular Go struct entity (generated from a container or list).
type GoStructCodeSnippet struct {
	// StructName is the name of the struct that is contained within the snippet.
	// It is stored such that callers can identify the struct to control where it
	// is output.
	StructName string
	// StructDef stores the code snippet that represents the struct that is
	// the input when code generation is performed.
	StructDef string
	// ListKeys stores code snippets that are associated with structs that are
	// generated to represent the keys of multi-key lists. In the case that the
	// Go struct for which the code is being generated does not contain a list
	// with multiple keys, this string is empty.
	ListKeys string
	// Methods contains code snippsets that represent functions that have the
	// input struct as a receiver, that help the user create new entries within
	// lists, without needing to populate the keys of the list.
	Methods string
	// Interfaces contains code snippets that represent interfaces that are
	// used within the generated struct. Used when there are interfaces that
	// represent multi-type unions generated.
	Interfaces string
	// enumTypeMap contains a map, keyed by a schema path (represented as a string)
	// to the underlying type names selected for that leaf. A slice of strings
	// is used for the type to handle cases where there is more than one enumerated
	// type returned for a leaf.
	enumTypeMap map[string][]string
}

// String returns the contents of the receiver GoStructCodeSnippet as a string.
func (g GoStructCodeSnippet) String() string {
	var b strings.Builder
	for _, s := range []string{g.StructDef, g.ListKeys, g.Methods, g.Interfaces} {
		genutil.WriteIfNotEmpty(&b, s)
	}
	return b.String()
}

// goEnumCodeSnippet is used to store the generated code snippets associated with
// a particular Go enumerated entity (generated from an identity, typedef referencing
// an enumerated value or leaf of type enumeration).
type goEnumCodeSnippet struct {
	// constDef stores the code snippet for the definition of the derived int64
	// type, and set of constants corresponding to the enumerated values of the
	// target YANG node.
	constDef string
	// valToString is a map of the int64 value used in the constant definition for
	// the enumeration to its definition. The definition consists of the string
	// name of the enumerated value in the YANG schema. In the case of identities
	// it also stores the module within which the identity was defined. This map
	// allows mapping of the enumerated value back to its original name.
	valToString map[int64]ygot.EnumDefinition
	// name is the name of the enumerated value, used for mapping purposes.
	name string
}

// goStructField contains a definition of a field within a Go struct.
type goStructField struct {
	Name string // Name is the field's name.
	Type string // Type is the Go type of the field.
	// IsScalarField represents whether the element is a leaf, rather than a
	// leaf-list or container. It is set to false explicitly where there are
	// scalar types that are not mapped to pointers (particularly, enumerated
	// types.
	IsScalarField bool
	Tags          string // Tags specifies the tags that should be used to annotate the field.
	// IsYANGContainer stores whether the field is a YANG container. This value
	// is used in templates to determine whether GetOrCreate methods should be
	// created.
	IsYANGContainer bool
	// IsYANGList stores whether the field is a YANG list. This value is used
	// in templates to determine whether GetXXX methods should be created using
	// the base template.
	IsYANGList bool
}

// goUnionInterface contains a definition of an interface that should
// be generated for a multi-type union in YANG.
type goUnionInterface struct {
	Name           string            // Name is the name of the interface
	Types          map[string]string // Types is a map keyed by the camelcase type name, with values of the Go types in the union.
	LeafPath       string            // LeafPath stores the path for the leaf for which the multi-type union is being generated.
	ParentReceiver string            // ParentReceiver is the name of the struct that is a parent of this union field. It is used to allow methods to be created which simplify handling the union in the calling code.
	TypeNames      []string          // TypeNames is an list of Go type names within the union.
}

// generatedGoStruct is used to repesent a Go structure to be handed to a template for output.
type generatedGoStruct struct {
	StructName string           // StructName is the name of the struct being output.
	YANGPath   string           // YANGPath is the schema path of the struct being output.
	Fields     []*goStructField // Fields is the slice of fields of the struct, described as goStructField structs.
}

// generatedGoMultiKeyListStruct is used to represent a struct used as a key of a YANG list that has multiple
// key elements.
type generatedGoMultiKeyListStruct struct {
	KeyStructName string          // KeyStructName is the name of the struct being output.
	Keys          []goStructField // Keys is a slice of goStructFields that are contained in the key struct.
	ParentPath    string          // ParentPath is the path to the list's parent in the YANG schema.
	ListName      string          // ListName is the name of the list itself in the YANG schema.
}

// generatedGoListMethod contains the fields required for generating the methods
// that are associated with a list entry within a struct representing a YANG entity.
type generatedGoListMethod struct {
	ListName  string          // ListName is the name of the list for which the method is being generated within its parent struct.
	ListType  string          // ListType is the type (struct name) of the element representing the list.
	Keys      []goStructField // Keys of the list that is being generated (length = 1 if the list is single keyed).
	KeyStruct string          // KeyStruct is the name of the struct used as a key for a multi-keyed list.
	Receiver  string          // Receiver is the name of the parent struct of the list, which is the receiver for the generated method.
}

// generatedGoKeyHelper contains the fields required for generating a method
// associated with a struct that is within a list in the YANG schema.
type generatedGoKeyHelper struct {
	// Receiver is the name of the type which acts as a receiver for a generated method.
	Receiver string
	// Keys specifies the keys of the list, as a map from YANG to Go identifier.
	Keys []*yangFieldMap
}

// yangFieldMap maps a YANG identifier to its Go identifier.
type yangFieldMap struct {
	// YANGName is the field's name in the YANG schema.
	YANGName string
	// GoName is the field's name in the Go struct.
	GoName string
	// IsPtr indicates that the key field is a pointer.
	IsPtr bool
}

// generatedGoEnumeration is used to represent a Go enumerated value to be handed
// to a template for output.
type generatedGoEnumeration struct {
	// EnumerationPrefix is the prefix that should be used to any value for
	// the generated output. For example, if EnumerationPrefix is set to
	// OpenconfigBGP_AfiSafi then the enumerated "AFI-SAFIs" will be named
	// OpenconfigBGP_AfiSafi_IPV4, etc., where "IPV4" is the name of one of
	// the enumerated values. The generated type that is referred to is the
	// EnumerationPrefix with a further prefix of E_ such that it can be
	// distinguished from a value of the enumeration in documentation.
	EnumerationPrefix string
	// Values is a map of numeric index to string which represents the valus of the
	// enumerated type. The numeric value may be explicitly assigned by the schema,
	// or populated by goyang during the parsing of the module.
	Values map[int64]string
}

// generatedLeafGetter is used to represent the parameters required to generate a
// getter for a leaf within the generated Go code.
type generatedLeafGetter struct {
	// Name is the name of the field. It is used as a suffix to Get to generate
	// the getter.
	Name string
	// Type is the type of the field, returned by the generated method.
	Type string
	// Zero is the value that should be returned if the field is set to nil.
	Zero string
	// Default is the default value specified in the YANG schema for the type
	// or leaf.
	Default *string
	// IsPtr stores whether the value is a pointer, such that it can be checked
	// against nil, or against the zero value.
	IsPtr bool
	// Receiver is the name of the receiver for the getter method.
	Receiver string
}

var (
	// goCommonHeaderTemplate is populated and output at the top of the generated code package
	goCommonHeaderTemplate = mustMakeTemplate("commonHeader", `
{{- /**/ -}}
/*
Package {{ .PackageName }} is a generated package which contains definitions
of structs which represent a YANG schema. The generated schema can be
compressed by a series of transformations (compression was {{ .CompressEnabled }}
in this case).

This package was generated by {{ .GeneratingBinary }}
using the following YANG input files:
{{- range $inputFile := .YANGFiles }}
	- {{ $inputFile }}
{{- end }}
Imported modules were sourced from:
{{- range $importPath := .IncludePaths }}
	- {{ $importPath }}
{{- end }}
*/
package {{ .PackageName }}

import (
	"encoding/json"
	"fmt"
	"reflect"

	"{{ .GoOptions.YgotImportPath }}"

{{- if .GenerateSchema }}
	"{{ .GoOptions.GoyangImportPath }}"
	"{{ .GoOptions.YtypesImportPath }}"
{{- end }}
{{- if .GoOptions.IncludeModelData }}
	gpb "{{ .GoOptions.GNMIProtoPath }}"
{{- end }}
)
`)

	// goOneOffHeaderTemplate defines the template for package code that should
	// be output in only one file.
	goOneOffHeaderTemplate = mustMakeTemplate("oneoffHeader", `
// {{ .BinaryTypeName }} is a type that is used for fields that have a YANG type of
// binary. It is used such that binary fields can be distinguished from
// leaf-lists of uint8s (which are mapped to []uint8, equivalent to
// []byte in reflection).
type {{ .BinaryTypeName }} []byte

// {{ .EmptyTypeName }} is a type that is used for fields that have a YANG type of
// empty. It is used such that empty fields can be distinguished from boolean fields
// in the generated code.
type {{ .EmptyTypeName }} bool

{{- if .GenerateSchema }}

var (
	SchemaTree map[string]*yang.Entry
)

func init() {
	var err error
	if SchemaTree, err = UnzipSchema(); err != nil {
		panic("schema error: " +  err.Error())
	}
}

// Schema returns the details of the generated schema.
func Schema() (*ytypes.Schema, error) {
	uzp, err := UnzipSchema()
	if err != nil {
		return nil, fmt.Errorf("cannot unzip schema, %v", err)
	}

	return &ytypes.Schema{
		Root: {{ .FakeRootName }},
		SchemaTree: uzp,
		Unmarshal: Unmarshal,
	}, nil
}

// UnzipSchema unzips the zipped schema and returns a map of yang.Entry nodes,
// keyed by the name of the struct that the yang.Entry describes the schema for.
func UnzipSchema() (map[string]*yang.Entry, error) {
	var schemaTree map[string]*yang.Entry
	var err error
	if schemaTree, err = ygot.GzipToSchema(ySchema); err != nil {
		return nil, fmt.Errorf("could not unzip the schema; %v", err)
	}
	return schemaTree, nil
}

// Unmarshal unmarshals data, which must be RFC7951 JSON format, into
// destStruct, which must be non-nil and the correct GoStruct type. It returns
// an error if the destStruct is not found in the schema or the data cannot be
// unmarshaled. The supplied options (opts) are used to control the behaviour
// of the unmarshal function - for example, determining whether errors are
// thrown for unknown fields in the input JSON.
func Unmarshal(data []byte, destStruct ygot.GoStruct, opts ...ytypes.UnmarshalOpt) error {
	tn := reflect.TypeOf(destStruct).Elem().Name()
	schema, ok := SchemaTree[tn]
	if !ok {
		return fmt.Errorf("could not find schema for type %s", tn )
	}
	var jsonTree interface{}
	if err := json.Unmarshal([]byte(data), &jsonTree); err != nil {
		return err
	}
	return ytypes.Unmarshal(schema, destStruct, jsonTree, opts...)
}

{{- end }}

{{- if .GoOptions.IncludeModelData }}
// ΓModelData contains the catalogue information corresponding to the modules for
// which Go code was generated.
var ΓModelData = []*gpb.ModelData{
{{- range $idx, $model := .ModelData }}
    {
		Name: "{{ .Name }}",
		{{- with $model.Organization }}
		Organization: "{{ . }}",
		{{- end }}
		{{- with $model.Version }}
		Version: "{{ . }}",
		{{- end }}
	},
{{- end }}
}
{{- end }}
`)
	// goStructTemplate takes an input generatedGoStruct, which contains a definition of
	// a container or list YANG schema node, and generates the Go code from it. The
	// Fields slice in the generatedGoStruct contains the child schema nodes of the
	// YANG schema node, along with the mapped Go type that is to be used to represent
	// them. The logic populating the generatedGoStruct handles non-scalar child schema
	// nodes: leaf-lists are mapped into slices; lists are mapped into a map or slice of
	// structs; and containers are mapped into structs.
	goStructTemplate = mustMakeTemplate("struct", `
// {{ .StructName }} represents the {{ .YANGPath }} YANG schema element.
type {{ .StructName }} struct {
{{- range $idx, $field := .Fields }}
	{{- if $field.IsScalarField }}
	{{ $field.Name }}	*{{ $field.Type }}	`+"`"+`{{ $field.Tags }}`+"`"+`
	{{- else }}
	{{ $field.Name }}	{{ $field.Type }}	`+"`"+`{{ $field.Tags }}`+"`"+`
	{{- end }}
{{- end }}
}

// IsYANGGoStruct ensures that {{ .StructName }} implements the yang.GoStruct
// interface. This allows functions that need to handle this struct to
// identify it as being generated by ygen.
func (*{{ .StructName }}) IsYANGGoStruct() {}
`)

	// goStructValidatorTemplate takes an input generatedGoStruct, which contains
	// a definition of a YANG schema node, and generates the Go validation code
	// from it.
	goStructValidatorTemplate = mustMakeTemplate("structValidator", `
// Validate validates s against the YANG schema corresponding to its type.
func (t *{{ .StructName }}) Validate(opts ...ygot.ValidationOption) error {
	if err := ytypes.Validate(SchemaTree["{{ .StructName }}"], t, opts...); err != nil {
		return err
	}
	return nil
}
`)

	// goContainerGetterTemplate defines a template that generates a getter function
	// for the field of a generated struct. It is generated only for YANG containers.
	goContainerGetterTemplate = mustMakeTemplate("getContainer", `
// Get{{ .Field.Name }} returns the value of the {{ .Field.Name }} struct pointer
// from {{ .StructName }}. If the receiver or the field {{ .Field.Name }} is nil, nil
// is returned such that the Get* methods can be safely chained.
func (t *{{ .StructName }}) Get{{ .Field.Name }}() {{ .Field.Type }} {
	if t != nil && t.{{ .Field.Name }} != nil {
		return t.{{ .Field.Name }}
	}
	return nil
}
`)

	// goGetOrCreateStructTemplate is a template that generates a getter
	// function for a struct field of the receiver struct. The function generated
	// creates the field if it does not exist.
	goGetOrCreateStructTemplate = mustMakeTemplate("getOrCreateStruct", `
// GetOrCreate{{ .Field.Name }} retrieves the value of the {{ .Field.Name }} field
// or returns the existing field if it already exists.
func (t *{{ .StructName }}) GetOrCreate{{ .Field.Name }}() {{ .Field.Type }} {
	if t.{{ .Field.Name }} != nil {
		return t.{{ .Field.Name }}
	}
	t.{{ .Field.Name }} = &{{ stripAsteriskPrefix .Field.Type }}{}
	return t.{{ .Field.Name }}
}
`)
	// goListKeyTemplate takes an input generatedGoMultiKeyListStruct, which is used to
	// describe the key of a list that has multiple keys, and generates a Go
	// struct definition that can be used to represent that key. For example, if a
	// YANG container or list contains a list, L:
	//
	//	container A {
	//		list L {
	//			key "key-one key-two";
	//			leaf key-one { type string; }
	//			leaf key-two { type uint32; }
	//		}
	//	}
	//
	// A struct is generated to represent the key of list L, of the following form:
	//
	//	type A_L_Key struct {
	//		KeyOne	string
	//		KeyTwo	uint32
	//	}
	//
	// This struct is then used as the key of the map representing the list L, in
	// the generated struct representing the container A.
	goListKeyTemplate = mustMakeTemplate("listkey", `
// {{ .KeyStructName }} represents the key for list {{ .ListName }} of element {{ .ParentPath }}.
type {{ .KeyStructName }} struct {
{{- range $idx, $key := .Keys }}
	{{ $key.Name }}	{{ $key.Type }}	`+"`{{ $key.Tags }}`"+`
{{- end }}
}
`)

	// goEnumDefinitionTemplate takes an input generatedGoEnumeration struct
	// and outputs the Go code that is associated with the enumerated type to be
	// generated.
	goEnumDefinitionTemplate = mustMakeTemplate("enumDefinition", `
// E_{{ .EnumerationPrefix }} is a derived int64 type which is used to represent
// the enumerated node {{ .EnumerationPrefix }}. An additional value named
// {{ .EnumerationPrefix }}_UNSET is added to the enumeration which is used as
// the nil value, indicating that the enumeration was not explicitly set by
// the program importing the generated structures.
type E_{{ .EnumerationPrefix }} int64

// IsYANGGoEnum ensures that {{ .EnumerationPrefix }} implements the yang.GoEnum
// interface. This ensures that {{ .EnumerationPrefix }} can be identified as a
// mapped type for a YANG enumeration.
func (E_{{ .EnumerationPrefix }}) IsYANGGoEnum() {}

// ΛMap returns the value lookup map associated with  {{ .EnumerationPrefix }}.
func (E_{{ .EnumerationPrefix }}) ΛMap() map[string]map[int64]ygot.EnumDefinition { return ΛEnum; }

// String returns a logging-friendly string for E_{{ .EnumerationPrefix }}.
func (e E_{{ .EnumerationPrefix }}) String() string {
	return ygot.EnumLogString(e, int64(e), "E_{{ .EnumerationPrefix }}")
}

{{ $enumName := .EnumerationPrefix -}}
const (
	{{- range $i, $val := .Values }}
	// {{ $enumName }}_{{ $val }} corresponds to the value {{ $val }} of {{ $enumName }}
	{{ $enumName }}_{{ $val }} E_{{ $enumName }} = {{ $i }}
	{{- end }}
)
`)
	// goNewListMemberTemplate takes an input generatedGoListMethod struct and
	// outputs a method, using the specified receiver, that creates a new instance
	// of a struct within a keyed YANG list, and populates the map key, and the
	// key fields of the list's struct according to the input arguments of the
	// function.
	goNewListMemberTemplate = mustMakeTemplate("newListEntry", `
// New{{ .ListName }} creates a new entry in the {{ .ListName }} list of the
// {{ .Receiver}} struct. The keys of the list are populated from the input
// arguments.
func (t *{{ .Receiver }}) New{{ .ListName }}(
  {{- $length := len .Keys -}}
  {{- range $i, $key := .Keys -}}
	{{ $key.Name }} {{ $key.Type -}}
	{{- if ne (inc $i) $length -}}, {{ end -}}
  {{- end -}}
  ) (*{{ .ListType }}, error){

	// Initialise the list within the receiver struct if it has not already been
	// created.
	if t.{{ .ListName }} == nil {
		{{- if ne .KeyStruct "" }}
		t.{{ .ListName }} = make(map[{{ .KeyStruct }}]*{{ .ListType }})
		{{- else }}
			{{- $listName := .ListName -}}
			{{- $listType := .ListType -}}
			{{- range $key := .Keys }}
		t.{{ $listName }} = make(map[{{ $key.Type }}]*{{ $listType }})
			{{- end }} {{- end }}
	}

	{{ if ne .KeyStruct "" -}}
	key := {{ .KeyStruct }}{
		{{- range $key := .Keys }}
		{{ $key.Name }}: {{ $key.Name }},
		{{- end }}
	}
	{{- else -}}
	{{- range $key := .Keys -}}
	key := {{ $key.Name }}
	{{- end -}}
	{{- end }}

	// Ensure that this key has not already been used in the
	// list. Keyed YANG lists do not allow duplicate keys to
	// be created.
	if _, ok := t.{{ .ListName }}[key]; ok {
		return nil, fmt.Errorf("duplicate key %v for list {{ .ListName }}", key)
	}

	t.{{ .ListName }}[key] = &{{ .ListType }}{
		{{- range $key := .Keys }}
		{{- if $key.IsScalarField }}
		{{ $key.Name }}: &{{ $key.Name }},
		{{- else }}
		{{ $key.Name }}: {{ $key.Name }},
		{{- end -}}
		{{- end }}
	}

	return t.{{ .ListName }}[key], nil
}
`)

	// goListGetterTemplate defines a template for a function that, for a particular
	// list key, gets an existing map value.
	goListGetterTemplate = mustMakeTemplate("getList", `
// Get{{ .ListName }} retrieves the value with the specified key from
// the {{ .ListName }} map field of {{ .Receiver }}. If the receiver is nil, or
// the specified key is not present in the list, nil is returned such that Get*
// methods may be safely chained.
func (t *{{ .Receiver }}) Get{{ .ListName }}(
  {{- $length := len .Keys -}}
  {{- range $i, $key := .Keys -}}
	{{ $key.Name }} {{ $key.Type -}}
	{{- if ne (inc $i) $length -}}, {{ end -}}
  {{- end -}}
  ) (*{{ .ListType }}){

	if t == nil {
		return nil
	}

  {{ if ne .KeyStruct "" -}}
	key := {{ .KeyStruct }}{
		{{- range $key := .Keys }}
		{{ $key.Name }}: {{ $key.Name }},
		{{- end }}
	}
	{{- else -}}
	{{- range $key := .Keys -}}
	key := {{ $key.Name }}
	{{- end -}}
	{{- end }}

  if lm, ok := t.{{ .ListName }}[key]; ok {
    return lm
  }
  return nil
}
`)

	// goGetOrCreateListTemplate defines a template for a function that, for a
	// particular list key, gets an existing map value, or creates it if it doesn't
	// exist.
	goGetOrCreateListTemplate = mustMakeTemplate("getOrCreateList", `
// GetOrCreate{{ .ListName }} retrieves the value with the specified keys from
// the receiver {{ .Receiver }}. If the entry does not exist, then it is created.
// It returns the existing or new list member.
func (t *{{ .Receiver }}) GetOrCreate{{ .ListName }}(
  {{- $length := len .Keys -}}
  {{- range $i, $key := .Keys -}}
	{{ $key.Name }} {{ $key.Type -}}
	{{- if ne (inc $i) $length -}}, {{ end -}}
  {{- end -}}
  ) (*{{ .ListType }}){

	{{ if ne .KeyStruct "" -}}
	key := {{ .KeyStruct }}{
		{{- range $key := .Keys }}
		{{ $key.Name }}: {{ $key.Name }},
		{{- end }}
	}
	{{- else -}}
	{{- range $key := .Keys -}}
	key := {{ $key.Name }}
	{{- end -}}
	{{- end }}

	if v, ok := t.{{ .ListName }}[key]; ok {
		return v
	}
	// Panic if we receive an error, since we should have retrieved an existing
	// list member. This allows chaining of GetOrCreate methods.
	v, err := t.New{{ .ListName }}(
		{{- range $i, $key := .Keys -}}
		{{ $key.Name }}
		{{- if ne (inc $i) $length -}}, {{ end -}}
		{{- end -}})
	if err != nil {
		panic(fmt.Sprintf("GetOrCreate{{ .ListName }} got unexpected error: %v", err))
	}
	return v
}
`)

	// goLeafGetterTemplate defines a template for a function that, for a
	// particular leaf, generates a getter method.
	goLeafGetterTemplate = mustMakeTemplate("getLeaf", `
// Get{{ .Name }} retrieves the value of the leaf {{ .Name }} from the {{ .Receiver }}
// struct. Caution should be exercised whilst using this method since it will return
// the Go zero value if the field is explicitly unset. If the caller explicitly does
// not care if {{ .Name }} is set, it can safely use t.Get{{ .Name }}()
// to retrieve the value. In the case that the caller has different actions based on
// whether the leaf is set or unset, it should use 'if t.{{ .Name }} == nil'
// before retrieving the leaf's value.
func (t *{{ .Receiver }}) Get{{ .Name }}() {{ .Type }} {
	if t == nil || t.{{ .Name }} == {{ if .IsPtr -}} nil {{- else }} {{ .Zero }} {{- end }} {
		{{- if .Default }}
		return {{ .Default }}
		{{- else }}
		return {{ .Zero }}
		{{- end }}
	}
	return {{ if .IsPtr -}} * {{- end -}} t.{{ .Name }}
}
`)

	// goDeleteListTemplate defines a template for a function that, for a
	// particular list key, deletes an existing map value.
	goDeleteListTemplate = mustMakeTemplate("deleteList", `
// Delete{{ .ListName }} deletes the value with the specified keys from
// the receiver {{ .Receiver }}. If there is no such element, the function
// is a no-op.
func (t *{{ .Receiver }}) Delete{{ .ListName }}(
  {{- $length := len .Keys -}}
  {{- range $i, $key := .Keys -}}
	{{ $key.Name }} {{ $key.Type -}}
	{{- if ne (inc $i) $length -}}, {{ end -}}
  {{- end -}}
  ) {
	{{ if ne .KeyStruct "" -}}
	key := {{ .KeyStruct }}{
		{{- range $key := .Keys }}
		{{ $key.Name }}: {{ $key.Name }},
		{{- end }}
	}
	{{- else -}}
	{{- range $key := .Keys -}}
	key := {{ $key.Name }}
	{{- end -}}
	{{- end }}

	delete(t.{{ .ListName }}, key)
}
`)

	// goListAppendTemplate defines a template for a function that takes an
	// input list member struct, extracts the key value, and appends it to a map.
	// In this template, since all list keys are specified to be pointer types
	// within values by default, we must invert the "IsScalarField" check to
	// ensure that we dereference elements that are pointers in the generated
	// code.
	goListAppendTemplate = mustMakeTemplate("appendList", `
// Append{{ .ListName }} appends the supplied {{ .ListType }} struct to the
// list {{ .ListName }} of {{ .Receiver }}. If the key value(s) specified in
// the supplied {{ .ListType }} already exist in the list, an error is
// returned.
func (t *{{ .Receiver }}) Append{{ .ListName }}(v *{{ .ListType }}) error {
	{{ if ne .KeyStruct "" -}}
	{{- range $key := .Keys }}
	{{- if $key.IsScalarField -}}
	if v.{{ $key.Name }} == nil {
		return fmt.Errorf("invalid nil key for {{ $key.Name }}")
	}

	{{ end -}}
	{{- end -}}
	key := {{ .KeyStruct }}{
		{{- range $key := .Keys }}
		{{- if $key.IsScalarField }}
		{{ $key.Name }}: *v.{{ $key.Name }},
		{{- else }}
		{{ $key.Name }}: v.{{ $key.Name }},
		{{- end -}} 
		{{ end }}
	}
	{{- else -}}
	{{- range $key := .Keys -}}
		{{- if $key.IsScalarField -}}
	if v.{{ $key.Name }} == nil {
		return fmt.Errorf("invalid nil key received for {{ $key.Name }}")
	}

	key := *v.{{ $key.Name }}
		{{- else -}}
	key := v.{{ $key.Name }}
		{{- end -}}
	{{- end -}}
	{{- end }}

	// Initialise the list within the receiver struct if it has not already been
	// created.
	if t.{{ .ListName }} == nil {
		{{- if ne .KeyStruct "" }}
		t.{{ .ListName }} = make(map[{{ .KeyStruct }}]*{{ .ListType }})
		{{- else }}
			{{- $listName := .ListName -}}
			{{- $listType := .ListType -}}
			{{- range $key := .Keys }}
		t.{{ $listName }} = make(map[{{ $key.Type }}]*{{ $listType }})
			{{- end }}
		{{- end }}
	}

	if _, ok := t.{{ .ListName }}[key]; ok {
		return fmt.Errorf("duplicate key for list {{ .ListName }} %v", key)
	}

	t.{{ .ListName }}[key] = v
	return nil
}
`)

	// goListMemberRenameTemplate provides a template for a function which renames
	// an entry within a list. It is used to generate functions for each list within
	// a generated Go struct.
	goListMemberRenameTemplate = mustMakeTemplate("renameListEntry", `
// Rename{{ .ListName }} renames an entry in the list {{ .ListName }} within
// the {{ .Receiver }} struct. The entry with key oldK is renamed to newK updating
// the key within the value.
func (t *{{ .Receiver }}) Rename{{ .ListName }}(
	{{- if ne .KeyStruct "" -}}
	oldK, newK {{ .KeyStruct -}}
  {{- else -}}
	{{- range $key := .Keys -}}
	oldK, newK {{ $key.Type -}}
	{{- end -}}
	{{- end -}}
) error {
	if _, ok := t.{{ .ListName }}[newK]; ok {
		return fmt.Errorf("key %v already exists in {{ .ListName }}", newK)
	}

	e, ok := t.{{ .ListName }}[oldK]
	if !ok {
		return fmt.Errorf("key %v not found in {{ .ListName }}", oldK)
	}

	{{- if ne .KeyStruct "" -}}
	{{- range $key := .Keys -}}
	{{- if $key.IsScalarField }}
	e.{{ $key.Name }} = &newK.{{ $key.Name }}
	{{- else }}
	e.{{ $key.Name }} = newK.{{ $key.Name }}
	{{- end -}}
	{{- end -}}
	{{ else -}}
	{{- $key := index .Keys 0 -}}
	{{- if $key.IsScalarField }}
	e.{{ $key.Name }} = &newK
	{{- else }}
	e.{{ $key.Name }} = newK
	{{- end -}}
	{{- end }}

	t.{{ .ListName }}[newK] = e
	delete(t.{{ .ListName }}, oldK)
	return nil
}
`)

	// goKeyMapTemplate defines the template for a function that is generated for a YANG
	// list type. It returns a map[string]interface{} keyed by the YANG leaf identifier of each
	// key leaf, and containing their values within the struct.
	goKeyMapTemplate = mustMakeTemplate("keyHelper", `
// ΛListKeyMap returns the keys of the {{ .Receiver }} struct, which is a YANG list entry.
func (t *{{ .Receiver }}) ΛListKeyMap() (map[string]interface{}, error) {
{{- range $key := .Keys -}}{{ if $key.IsPtr }}
	if t.{{ $key.GoName }} == nil {
		return nil, fmt.Errorf("nil value for key {{ $key.GoName }}")
	}
	{{- end }}
{{ end }}
	return map[string]interface{}{
		{{- range $key := .Keys }}
		"{{ $key.YANGName }}": {{ if $key.IsPtr -}}
		*
		{{- end -}} t.{{ $key.GoName }},
		{{- end }}
	}, nil
}
`)

	// goEnumMapTemplate provides a template to output a constant map which
	// can be used to resolve the string value of any enumeration within the
	// schema.
	goEnumMapTemplate = mustMakeTemplate("enumMap", `
// ΛEnum is a map, keyed by the name of the type defined for each enum in the
// generated Go code, which provides a mapping between the constant int64 value
// of each value of the enumeration, and the string that is used to represent it
// in the YANG schema. The map is named ΛEnum in order to avoid clash with any
// valid YANG identifier.
var ΛEnum = map[string]map[int64]ygot.EnumDefinition{
	{{- range $enumName, $enumValues := . }}
	"E_{{ $enumName }}": {
		{{- range $value, $valDef := $enumValues }}
		{{ $value }}: {Name: "{{ $valDef.Name }}"
			{{- if ne $valDef.DefiningModule "" -}}
				, DefiningModule: "{{ $valDef.DefiningModule }}"
			{{- end -}}
		},
		{{- end }}
	},
	{{- end }}
}
`)

	// goEnumTypeMapTemplate provides a template to output a constant map which
	// can be used to resolve a schemapath to the set of enumerated types that
	// are valid for the leaf or leaf-list defined at the path specified.
	goEnumTypeMapTemplate = mustMakeTemplate("enumTypeMap", `
// ΛEnumTypes is a map, keyed by a YANG schema path, of the enumerated types that
// correspond with the leaf. The type is represented as a reflect.Type. The naming
// of the map ensures that there are no clashes with valid YANG identifiers.
var ΛEnumTypes = map[string][]reflect.Type{
  {{- range $schemapath, $types := . }}
	"{{ $schemapath }}": []reflect.Type{
		{{- range $i, $t := $types }}
		reflect.TypeOf(({{ $t }})(0)),
		{{- end }}
	},
	{{- end }}
}
`)

	// goEnumTypeMapAccessTemplate provides a template to output an accessor
	// function with a generated struct as receiver, it returns the enum type
	// map associated with the generated code.
	goEnumTypeMapAccessTemplate = mustMakeTemplate("enumTypeMapAccessor", `
// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
// that are included in the generated code.
func (t *{{ .StructName }}) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
`)

	// schemaVarTemplate provides a template to output a constant byte
	// slice which contains the serialised schema of the YANG modules for
	// which code generation was performed.
	schemaVarTemplate = mustMakeTemplate("schemaVar", `
var (
	// {{ .VarName }} is a byte slice contain a gzip compressed representation of the
	// YANG schema from which the Go code was generated. When uncompressed the
	// contents of the byte slice is a JSON document containing an object, keyed
	// on the name of the generated struct, and containing the JSON marshalled
	// contents of a goyang yang.Entry struct, which defines the schema for the
	// fields within the struct.
	{{ .VarName }} = []byte{
{{- range $i, $line := .Schema }}
		{{ $line }}
{{- end }}
	}
)
`)

	// unionTypeTemplate outputs the type that corresponds to a multi-type union
	// in the YANG schema.
	unionTypeTemplate = mustMakeTemplate("unionType", `
// {{ .Name }} is an interface that is implemented by valid types for the union
// for the leaf {{ .LeafPath }} within the YANG schema.
type {{ .Name }} interface {
	Is_{{ .Name }}()
}
{{ $intfName := .Name -}}
{{- $path := .LeafPath -}}
{{- range $typeName, $type := .Types }}
// {{ $intfName }}_{{ $typeName }} is used when {{ $path }}
// is to be set to a {{ $type }} value.
type {{ $intfName }}_{{ $typeName }} struct {
	{{ $typeName }}	{{ $type }}
}

// Is_{{ $intfName }} ensures that {{ $intfName }}_{{ $typeName }}
// implements the {{ $intfName }} interface.
func (*{{ $intfName }}_{{ $typeName }}) Is_{{ $intfName }}() {}
{{ end }}
`)

	// unionHelperTemplate defines a template that defines a helper method
	// with a particular receiver type that allows an input type to be converted
	// to its corresponding type in the union type.
	unionHelperTemplate = mustMakeTemplate("unionHelper", `
{{- $intfName := .Name -}}
{{- $path := .LeafPath -}}
// To_{{ .Name }} takes an input interface{} and attempts to convert it to a struct
// which implements the {{ .Name }} union. It returns an error if the interface{} supplied
// cannot be converted to a type within the union.
func (t *{{ .ParentReceiver }}) To_{{ .Name }}(i interface{}) ({{ .Name }}, error) {
	switch v := i.(type) {
	{{ range $typeName, $type := .Types -}}
	case {{ $type }}:
		return &{{ $intfName }}_{{ $typeName }}{v}, nil
	{{ end -}}
	default:
		return nil, fmt.Errorf("cannot convert %v to {{ .Name }}, unknown union type, got: %T, want any of [
		{{- $length := len .TypeNames -}}
		{{- range $i, $type := .TypeNames -}}
			{{ $type }}
			{{- if ne (inc $i) $length -}}, {{ end -}}
		{{- end -}}
		]", i, i)
	}
}
`)

	// templateHelperFunctions specifies a set of functions that are supplied as
	// helpers to the templates that are used within this file.
	templateHelperFunctions = template.FuncMap{
		// inc provides a means to add 1 to a number, and is used within templates
		// to check whether the index of an element within a loop is the last one,
		// such that special handling can be provided for it (e.g., not following
		// it with a comma in a list of arguments).
		"inc": func(i int) int {
			return i + 1
		},
		"toUpper": strings.ToUpper,
		"indentLines": func(s string) string {
			var b bytes.Buffer
			p := strings.Split(s, "\n")
			b.WriteRune('\n')
			for i, l := range p {
				if l == "" {
					continue
				}
				b.WriteString(fmt.Sprintf("  %s", l))
				if i != len(p)-1 {
					b.WriteRune('\n')
				}
			}
			return b.String()
		},
		// stripAsteriskPrefix provides a template helper that removes an asterisk
		// from the start of a string. It is used to remove "*" from the start of
		// pointer types.
		"stripAsteriskPrefix": func(s string) string { return strings.TrimPrefix(s, "*") },
	}
)

// mustMakeTemplate generates a template.Template for a particular named source
// template; with a common set of helper functions.
func mustMakeTemplate(name, src string) *template.Template {
	return template.Must(template.New(name).Funcs(templateHelperFunctions).Parse(src))
}

// writeGoHeader outputs the package header, including the package name and
// comments that is to be included with the generated code. The input set of
// files (yangFiles) are output to indicate the modules for which code
// generation was targeted, along with the includePaths indicating where
// imported modules were sourced from. If the cfg.GoOptions.YgotImport path
// is not set, then it is set to the value of GoDefaultYgotImportPath. In a similar manner
// an unset cfg.GoOptions.GoyangImportPath results in the goyang path being set to
// GoDefaultYgotImportPath, and an unset cfg.GoOptions.YtypesImportPath results in the
// path for ytypes being set to GoDefaultYtypesImportPath. The supplied rootName is the
// name of the fake root struct, if it was produced - and is used to output a schema
// definition in the file header.
//
// The header returned is split into two strings, the common header is a header that
// should be used for all files within the output package. The one off header should
// be included in only one file of the package.
func writeGoHeader(yangFiles, includePaths []string, cfg GeneratorConfig, rootName string, modelData []*gpb.ModelData) (string, string, error) {
	// Determine the running binary's name.
	if cfg.Caller == "" {
		cfg.Caller = genutil.CallerName()
	}

	if cfg.PackageName == "" {
		cfg.PackageName = defaultPackageName
	}

	if cfg.GoOptions.YgotImportPath == "" {
		cfg.GoOptions.YgotImportPath = genutil.GoDefaultYgotImportPath
	}
	if cfg.GoOptions.GoyangImportPath == "" {
		cfg.GoOptions.GoyangImportPath = genutil.GoDefaultGoyangImportPath
	}
	if cfg.GoOptions.YtypesImportPath == "" {
		cfg.GoOptions.YtypesImportPath = genutil.GoDefaultYtypesImportPath
	}
	if cfg.GoOptions.GNMIProtoPath == "" {
		cfg.GoOptions.GNMIProtoPath = genutil.GoDefaultGNMIImportPath
	}

	// Build input to the header template which stores parameters which are included
	// in the header of generated code.
	s := struct {
		PackageName      string           // PackgeName is the name of the package to be generated.
		YANGFiles        []string         // YANGFiles contains the list of input YANG source files for code generation.
		IncludePaths     []string         // IncludePaths contains the list of paths that included modules were searched for in.
		CompressEnabled  bool             // CompressEnabled indicates whether compression is enabled.
		GeneratingBinary string           // GeneratingBinary is the name of the binary generating the code.
		GenerateSchema   bool             // GenerateSchema stores whether the generator requested that the schema was to be stored with the output code.
		GoOptions        GoOpts           // GoOptions stores additional Go-specific options for the output code, including package paths.
		BinaryTypeName   string           // BinaryTypeName is the name of the type used for YANG binary types.
		EmptyTypeName    string           // EmptyTypeName is the name of the type used for YANG empty types.
		FakeRootName     string           // FakeRootName is the name of the fake root struct in the YANG type
		ModelData        []*gpb.ModelData // ModelData contains the gNMI ModelData definition for the input types.
	}{
		PackageName:      cfg.PackageName,
		YANGFiles:        yangFiles,
		IncludePaths:     includePaths,
		CompressEnabled:  cfg.TransformationOptions.CompressBehaviour.CompressEnabled(),
		GeneratingBinary: cfg.Caller,
		GenerateSchema:   cfg.GenerateJSONSchema,
		GoOptions:        cfg.GoOptions,
		BinaryTypeName:   ygot.BinaryTypeName,
		EmptyTypeName:    ygot.EmptyTypeName,
		ModelData:        modelData,
	}

	s.FakeRootName = "nil"
	if cfg.TransformationOptions.GenerateFakeRoot && rootName != "" {
		s.FakeRootName = fmt.Sprintf("&%s{}", rootName)
	}

	var common bytes.Buffer
	if err := goCommonHeaderTemplate.Execute(&common, s); err != nil {
		return "", "", err
	}

	var oneoff bytes.Buffer
	if err := goOneOffHeaderTemplate.Execute(&oneoff, s); err != nil {
		return "", "", err
	}

	return common.String(), oneoff.String(), nil
}

// IsScalarField determines which fields should be converted to pointers when
// outputting structs; this is done to allow checks against nil.
func IsScalarField(field *yang.Entry, t *MappedType) bool {
	switch {
	// A non-leaf has a generated type which are always stored by pointers.
	case field.Kind != yang.LeafEntry:
		return false
	// A union shouldn't be a pointer since its field type is an interface;
	case len(t.UnionTypes) >= 2:
		return false
	// an enumerated value shouldn't be a pointer either since its has an UNSET value;
	case t.IsEnumeratedValue:
		return false
	// an unmapped type (interface{}), byte slice, or a leaflist can also use nil already, so they should also not be pointers.
	case t.NativeType == ygot.BinaryTypeName, t.NativeType == ygot.EmptyTypeName, t.NativeType == "interface{}", field.ListAttr != nil:
		return false
	}
	return true
}

// writeGoStruct generates code snippets for targetStruct. The parameter goStructElements
// contains other Directory structs for which code is being generated, that may be referenced
// during the generation of the code corresponding to targetStruct (e.g., to determine a
// child container's struct name).
//
// writeGoStruct takes the following additional arguments:
//  - state - the current generator state, as a genState pointer.
//  - compressOCPaths - a bool indicating whether OpenConfig path compression is enabled for
//    this schema.
//  - generateJSONSchema - a bool indicating whether the generated code should include the
//    JSON representation of the YANG schema for this element.
//  - goOpts - Go specific code generation options as a GoOpts struct.
//  - skipEnumDedup -- a boolean that indicates whether leaves of type enumeration  that are
//    used in multiple places in the schema tree should share a common underlying type.
//
// writeGoStruct returns a GoStructCodeSnippet which contains
//	1. The generated struct for targetStruct (structDef)
//	2. Additional generated structs that are keys for any multi-key lists that are children
//	   of targetStruct (listKeys).
//	3. Methods with the struct corresponding to targetStruct as a receiver, e.g., for each
//	   list a NewListMember() method is generated.
func writeGoStruct(targetStruct *Directory, goStructElements map[string]*Directory, gogen *goGenState, compressPaths, generateJSONSchema, skipEnumDedup, shortenEnumLeafNames, useDefiningModuleForTypedefEnumNames bool, goOpts GoOpts) (GoStructCodeSnippet, []error) {
	var errs []error

	// structDef is used to store the attributes of the structure for which code is being
	// generated.
	structDef := generatedGoStruct{
		StructName: targetStruct.Name,
		YANGPath:   util.SlicePathToString(targetStruct.Path),
	}

	// associatedListKeyStructs is a slice containing the key structures for any multi-keyed
	// lists that are fields of the struct.
	associatedListKeyStructs := []*generatedGoMultiKeyListStruct{}

	// associatedListMethods is a slice of pointers to generatedGoListMethod structs
	// which describe methods that use the target struct as a receiver. These structs
	// represent the methods that are used as helpers such as those methods that allow
	// a new member to be created within the list (populating the keys), and an
	// existing list member to be appended to the list.
	var associatedListMethods []*generatedGoListMethod

	// associatedLeafGetters is a slice of structs which define the set of leaf getters
	// to generated for the struct. It is only populated if the GenerateLeafGetters option
	// is set to true.
	var associatedLeafGetters []*generatedLeafGetter

	// The Go names of the struct's fields.
	goFieldNameMap := GoFieldNameMap(targetStruct)

	// definedNameMap defines a map, keyed by YANG identifier to the Go struct field name.
	definedNameMap := map[string]*yangFieldMap{}

	// enumTypeMap stores a map of schemapath to type name for enumerated types.
	enumTypeMap := map[string][]string{}

	// genUnions stores the set of multi-type YANG unions that must have
	// code generated for them.
	genUnions := []goUnionInterface{}
	// genUnionSet stores a set of union type names such that we can process
	// each appearance of a union type within the struct once and only once.
	genUnionSet := map[string]bool{}

	annotationPrefix := goOpts.AnnotationPrefix
	// Set the default annotation prefix if it is unset.
	if goOpts.AnnotationPrefix == "" {
		annotationPrefix = DefaultAnnotationPrefix
	}

	if goOpts.AddAnnotationFields {
		// Add the top-level struct metadata field.
		structDef.Fields = append(structDef.Fields, &goStructField{
			Name: fmt.Sprintf("%sMetadata", annotationPrefix),
			Type: annotationFieldType,
			Tags: `path:"@" ygotAnnotation:"true"`,
		})
	}

	// Alphabetically order fields to produce deterministic output.
	for _, fName := range GetOrderedFieldNames(targetStruct) {
		// Iterate through the fields of the struct that we are generating code for.
		// For each field, calculate the name of the field (ensuring that it is unique), and
		// the corresponding type. fieldDef is used to store the definition of the field (name
		// and type) that are calculated.
		var fieldDef *goStructField

		field := targetStruct.Fields[fName]
		fieldName := goFieldNameMap[fName]
		definedNameMap[fName] = &yangFieldMap{YANGName: fName, GoName: fieldName}

		switch {
		case field.IsList():
			// If the field within the struct is a list, then generate code for this list. This
			// includes extracting any new types that are required to represent the key of a
			// list that has multiple keys.
			fieldType, multiKeyListKey, listMethods, listErr := yangListFieldToGoType(field, fieldName, targetStruct, goStructElements, gogen)
			if listErr != nil {
				errs = append(errs, listErr)
			}

			fieldDef = &goStructField{
				Name:       fieldName,
				Type:       fieldType,
				IsYANGList: true,
			}

			if listMethods != nil {
				associatedListMethods = append(associatedListMethods, listMethods)
			}

			if multiKeyListKey != nil {
				// If the list had multiple keys, add the struct that represented the list
				// type to the slice of those that should have code generated for them.
				associatedListKeyStructs = append(associatedListKeyStructs, multiKeyListKey)
			}

		case field.IsContainer():
			// This is a YANG container, so it is represented in code using a pointer to the struct type that
			// is defined for the entity. findMappableEntities has already determined which fields are to
			// be output, so no filtering of the set of fields is required here.
			structName, ok := gogen.uniqueDirectoryNames[field.Path()]
			if !ok {
				errs = append(errs, fmt.Errorf("could not resolve %s into a defined struct", field.Path()))
				continue
			}

			fieldDef = &goStructField{
				Name:            fieldName,
				Type:            fmt.Sprintf("*%s", structName),
				IsYANGContainer: true,
			}
		case field.IsLeaf() || field.IsLeafList():
			// This is a leaf or leaf-list, so we map it into the Go type that corresponds to the
			// YANG type that the leaf represents.
			mtype, err := gogen.yangTypeToGoType(resolveTypeArgs{yangType: field.Type, contextEntry: field}, compressPaths, skipEnumDedup, shortenEnumLeafNames, useDefiningModuleForTypedefEnumNames)
			if err != nil {
				errs = append(errs, err)
				continue
			}

			// Set the default type to the mapped Go type.
			fType := mtype.NativeType
			schemapath := util.SchemaTreePathNoModule(field)
			zeroValue := mtype.ZeroValue
			defaultValue := goLeafDefault(field, mtype)

			// Only if this union has more than one subtype do we generate the union;
			// otherwise, we use that subtype directly.
			// Also, make sure to process a union type once and only once within the struct.
			// Even if the union has already been processed in another struct, we still need
			// to generate the union helper with this struct as the receiver and do other
			// processing as well. On the other hand, if the union type is used by multiple
			// fields, we assume that it's due to a leafref and use the same union name
			// without doing further code generation. XXX(wenbli): It's possible that it's a
			// name collision instead, but because of its low chance and the extra complexity
			// required to resolve it (e.g. storing the union entry so we make sure the name
			// is used for the right union entry), we ignore it and allow wrong code to be
			// generated.
			if len(mtype.UnionTypes) > 1 && !genUnionSet[mtype.NativeType] {
				genUnionSet[mtype.NativeType] = true

				intf := goUnionInterface{
					Name:           mtype.NativeType,
					Types:          map[string]string{},
					LeafPath:       field.Path(),
					ParentReceiver: targetStruct.Name,
				}

				for t := range mtype.UnionTypes {
					// If the type within the union is not a builtin type then we store
					// it within the enumMap, since it is an enumerated type.
					if _, builtin := validGoBuiltinTypes[t]; !builtin {
						enumTypeMap[schemapath] = append(enumTypeMap[schemapath], t)
					}

					tn := yang.CamelCase(t)
					// Ensure that we sanitise the type name to be used in the
					// output struct.
					if t == "interface{}" {
						tn = "Interface"
					}
					intf.Types[tn] = t
					intf.TypeNames = append(intf.TypeNames, t)
				}
				// Sort the names of the types into determinstic order.
				sort.Strings(intf.TypeNames)

				genUnions = append(genUnions, intf)
			}

			if field.ListAttr != nil {
				// If the field's ListAttr is set, then this indicates that this
				// element is a leaf-list. We represent a leaf-list in the output
				// code using a slice of the type that the element was mapped to.
				fType = fmt.Sprintf("[]%s", fType)
				// Slices have a nil zero value rather than the value of their
				// underlying type.
				zeroValue = "nil"
			}

			scalarField := IsScalarField(field, mtype)

			definedNameMap[fName].IsPtr = scalarField
			if mtype.IsEnumeratedValue {
				// Any enumerated type is stored in the enumMap to allow for type
				// resolution from a schema path.
				enumTypeMap[schemapath] = append(enumTypeMap[schemapath], mtype.NativeType)
			}

			if goOpts.GenerateLeafGetters {
				// If we are generating leaf getters, then append the relevant information
				// to the associatedLeafGetters slice to be generated along with other
				// associated methods.
				associatedLeafGetters = append(associatedLeafGetters, &generatedLeafGetter{
					Name:     fieldName,
					Type:     fType,
					Zero:     zeroValue,
					IsPtr:    scalarField,
					Receiver: targetStruct.Name,
					Default:  defaultValue,
				})
			}

			fieldDef = &goStructField{
				Name:          fieldName,
				Type:          fType,
				IsScalarField: scalarField,
			}
		default:
			errs = append(errs, fmt.Errorf("unknown entity type for mapping to Go: %s, Kind: %v", field.Path(), field.Kind))
			continue
		}

		// Find the schema paths that the field corresponds to, such that these can
		// be used as annotations (tags) within the generated struct. Go paths are
		// always relative.
		schemaMapPaths, err := findMapPaths(targetStruct, fName, compressPaths, false)
		if err != nil {
			errs = append(errs, err)
			continue
		}

		var tagBuf bytes.Buffer
		tagBuf.WriteString(`path:"`)
		var metadataTagBuf bytes.Buffer
		metadataTagBuf.WriteString(`path:"`)
		for i, p := range schemaMapPaths {
			tagBuf.WriteString(util.SlicePathToString(p))

			p[len(p)-1] = fmt.Sprintf("@%s", p[len(p)-1])
			metadataTagBuf.WriteString(util.SlicePathToString(p))

			if i != len(schemaMapPaths)-1 {
				tagBuf.WriteRune('|')
				metadataTagBuf.WriteRune('|')
			}
		}
		tagBuf.WriteByte('"')
		metadataTagBuf.WriteString(`" ygotAnnotation:"true"`)

		// Append a tag indicating the module that instantiates this field.
		im, err := field.InstantiatingModule()
		if err != nil {
			// This is a non-fatal error, since it can only occur in testing. All YANG modules
			// must have a specified namespace.
			log.Infof("field %s has a nil module, error discarded", field.Path())
		} else {
			tagBuf.WriteString(fmt.Sprintf(` module:"%s"`, im))
		}

		fieldDef.Tags = tagBuf.String()

		// Append the generated field definition to the set of fields of the struct.
		structDef.Fields = append(structDef.Fields, fieldDef)

		if goOpts.AddAnnotationFields {
			// Append the definition of the field annotation to the set of fields in the
			// struct.
			structDef.Fields = append(structDef.Fields, &goStructField{
				Name: fmt.Sprintf("%s%s", annotationPrefix, fieldDef.Name),
				Type: annotationFieldType,
				Tags: metadataTagBuf.String(),
			})
		}
	}

	// structBuf is used to store the code associated with the struct defined for
	// the target YANG entity.
	var structBuf bytes.Buffer
	if err := goStructTemplate.Execute(&structBuf, structDef); err != nil {
		errs = append(errs, err)
	}

	// listkeyBuf is a buffer which stores the code associated with structs that
	// are associated with the structs generated to act as list keys.
	var listkeyBuf bytes.Buffer
	for _, listKey := range associatedListKeyStructs {
		if err := goListKeyTemplate.Execute(&listkeyBuf, listKey); err != nil {
			errs = append(errs, err)
		}
	}

	// methodBuf is used to store the code generated for methods that have the
	// target entity's generated struct as a receiver.
	var methodBuf bytes.Buffer
	for _, method := range associatedListMethods {
		if err := goNewListMemberTemplate.Execute(&methodBuf, method); err != nil {
			errs = append(errs, err)
		}

		if goOpts.GenerateRenameMethod {
			if err := goListMemberRenameTemplate.Execute(&methodBuf, method); err != nil {
				errs = append(errs, err)
			}
		}

		if goOpts.GenerateGetters {
			if err := generateGetOrCreateList(&methodBuf, method); err != nil {
				errs = append(errs, err)
			}
			if err := generateListGetter(&methodBuf, method); err != nil {
				errs = append(errs, err)
			}
		}

		if goOpts.GenerateDeleteMethod {
			if err := generateListDelete(&methodBuf, method); err != nil {
				errs = append(errs, err)
			}
		}

		if goOpts.GenerateAppendMethod {
			if err := generateListAppend(&methodBuf, method); err != nil {
				errs = append(errs, err)
			}
		}
	}

	if goOpts.GenerateGetters {
		if err := generateGetOrCreateStruct(&methodBuf, structDef); err != nil {
			errs = append(errs, err)
		}
		if err := generateContainerGetters(&methodBuf, structDef); err != nil {
			errs = append(errs, err)
		}
	}

	if goOpts.GenerateLeafGetters {
		if err := generateLeafGetters(&methodBuf, associatedLeafGetters); err != nil {
			errs = append(errs, err)
		}
	}

	if err := generateGetListKey(&methodBuf, targetStruct, definedNameMap); err != nil {
		errs = append(errs, err)
	}

	// interfaceBuf is used to store the code generated for interfaces that
	// are used for multi-type unions within the struct.
	var interfaceBuf bytes.Buffer
	for _, intf := range genUnions {
		if _, ok := gogen.generatedUnions[intf.Name]; !ok {
			if err := unionTypeTemplate.Execute(&interfaceBuf, intf); err != nil {
				errs = append(errs, err)
			}
			gogen.generatedUnions[intf.Name] = true
		}
		if err := unionHelperTemplate.Execute(&interfaceBuf, intf); err != nil {
			errs = append(errs, err)
		}

	}

	if generateJSONSchema {
		if err := generateValidator(&methodBuf, structDef); err != nil {
			errs = append(errs, err)
		}

		if err := generateEnumTypeMapAccessor(&methodBuf, structDef); err != nil {
			errs = append(errs, err)
		}
	}

	return GoStructCodeSnippet{
		StructName:  structDef.StructName,
		StructDef:   structBuf.String(),
		Methods:     methodBuf.String(),
		ListKeys:    listkeyBuf.String(),
		Interfaces:  interfaceBuf.String(),
		enumTypeMap: enumTypeMap,
	}, errs
}

// generateValidator generates a validation function string for structDef and
// appends it to the supplied buffer.
// Assuming structDef represents the following struct:
//
//   type MyStruct struct {
//     field1 *string
//   }
//
// the validation function generated for the struct will be:
//
//   func (t *MyStruct) Validate(value interface{}) error {
//     if err := ytypes.Validate(schemaMap["MyStruct"], value); err != nil {
//       return err
//     }
//     return nil
//   }
func generateValidator(buf *bytes.Buffer, structDef generatedGoStruct) error {
	return goStructValidatorTemplate.Execute(buf, structDef)
}

// goTmplFieldDetails stores a goStructField along with additional details
// corresponding to it. It is used withAin templates that handle individual
// fields.
type goTmplFieldDetails struct {
	Field      *goStructField // Field stores the definition of the field with which other details are associated.
	StructName string         // StructName is the name of the struct that the field is a member of.
}

// generateGetOrCreateStruct generates a getter method for the YANG container
// (Go struct ptr) fields of structDef, and appends it to the supplied buffer.
// Assuming that structDef represents the following struct:
//
//  type MyStruct struct {
// 		Container *MyStruct_Container
//  }
//
// the getter function generated for the struct will be:
//
//  func (s *MyStruct) GetOrCreateContainer() *MyStruct_Container {
//    if s.Container != nil {
//      return s.Container
//    }
//    s.Container = &MyStruct_Container{}
//    return s.Container
//  }
func generateGetOrCreateStruct(buf *bytes.Buffer, structDef generatedGoStruct) error {
	for _, f := range structDef.Fields {
		if f.IsYANGContainer {
			tmpStruct := goTmplFieldDetails{
				StructName: structDef.StructName,
				Field:      f,
			}
			if err := goGetOrCreateStructTemplate.Execute(buf, tmpStruct); err != nil {
				return err
			}
		}
	}
	return nil
}

// generateContainerGetters generates GetXXX methods for container fields of the
// supplied struct described by structDef.
//
// The method is defined by the goContainerGetterTemplate. This template returns
// the value if the field is set, otherwise returns nil. The generated getters
// are safe to call with a nil receiver.
func generateContainerGetters(buf *bytes.Buffer, structDef generatedGoStruct) error {
	for _, f := range structDef.Fields {
		// Only YANG containers have getters generated for them.
		if !f.IsYANGContainer {
			continue
		}
		tmpStruct := goTmplFieldDetails{
			StructName: structDef.StructName,
			Field:      f,
		}
		if err := goContainerGetterTemplate.Execute(buf, tmpStruct); err != nil {
			return err
		}
	}
	return nil
}

// generateLeafGetters generates GetXXX methods for the leaf fields described by
// the supplied slice of generatedLeafGetter structs.
func generateLeafGetters(buf *bytes.Buffer, leaves []*generatedLeafGetter) error {
	var errs errlist.List
	for _, l := range leaves {
		if err := goLeafGetterTemplate.Execute(buf, l); err != nil {
			errs.Add(err)
		}
	}
	return errs.Err()
}

// generateGetOrCreateList generates a getter function similar to that created
// by the generateGetOrCreateStruct function for maps within the generated Go
// code (which represent YANG lists). It handles both simple and composite key
// lists.
//
// If the list described has a single key, the argument to the function is the
// non-pointer key value. If the list has a complex type, it is an instance of
// the generated key type for the list.
//
// The generated function returns the existing value if the key exists in the
// list, or creates a new value using the NewXXX method if it does not exist.
// The generated function is written to the supplied buffer, using the method
// argument to determine the list's characteristics in the template.
func generateGetOrCreateList(buf *bytes.Buffer, method *generatedGoListMethod) error {
	return goGetOrCreateListTemplate.Execute(buf, method)
}

// generateListGetter generates a getter function for members of the a YANG list
// (Go map) field of the input struct. The generated function takes arguments
// of the same form as those that are given to the GetOrCreate method generated
// by generateGetOrCreateList.
func generateListGetter(buf *bytes.Buffer, method *generatedGoListMethod) error {
	return goListGetterTemplate.Execute(buf, method)
}

// generateListDelete generates a delete function for members of the a YANG list
// (Go map) field of the input struct. The generated function takes arguments
// of the same form as those that are given to the GetOrCreate method generated
// by generateGetOrCreateList.
func generateListDelete(buf *bytes.Buffer, method *generatedGoListMethod) error {
	return goDeleteListTemplate.Execute(buf, method)
}

// generateListAppend generates a function which appends a (key, value) to a
// Go map (YANG list) within the generated code. The argument of the generated
// function is the map's member type - from which the key values are extracted.
// The generated function returns an error if the key already exists in the list.
//
// The generated function is written to the supplied buffer - using the supplied
// method argument to determine the list's characteristics in the template.
func generateListAppend(buf *bytes.Buffer, method *generatedGoListMethod) error {
	return goListAppendTemplate.Execute(buf, method)
}

// generateGetListKey generates a function extracting the keys from a list
// defined in the Directory s, and appends it to the supplier buffer. The
// nameMap stores maps between the key YANG field identifiers and their Go
// identifiers.
//
// If the input Directory is the following list entry:
//
//  list foo {
//    key "bar baz";
//
//    leaf bar { type string; }
//    leaf baz { type uint8; }
//    leaf colour { type string; }
//  }
//
// Which is mapped into the Go struct:
//
//  type Foo {
//    Bar *string `path:"bar"`
//    Baz *uint8  `path:"baz"`
//    Colour *string `path:"colour"`
//  }
//
// The generated method will;
//  - Check pointer keys to ensure they are non-nil.
//  - Return a map[string]interface{} keyed by the name of the key in the YANG schema, with the value
//    specified in the struct.
//
// i.e.: for the above struct:
//
//  func (t *Foo) ΛListKeyMap() (map[string]interface{}, error) {
//	if t.Bar == nil {
//	   return nil, fmt.Errorf("key value for Bar is nil")
//	}
//
//	if t.Baz == nil {
//	   return nil, fmt.Errorf("key value for Baz is nil")
//	}
//
//	return map[string]interface{}{
//	  "bar": *t.Bar,
//	  "baz": *t.Baz,
//	}
//  }
func generateGetListKey(buf *bytes.Buffer, s *Directory, nameMap map[string]*yangFieldMap) error {
	if !s.isList() {
		return nil
	}

	h := generatedGoKeyHelper{
		Receiver: s.Name,
	}

	kn := []string{}
	for k := range s.ListAttr.Keys {
		kn = append(kn, k)
	}
	sort.Strings(kn)

	for _, k := range kn {
		h.Keys = append(h.Keys, nameMap[k])
	}

	return goKeyMapTemplate.Execute(buf, h)
}

// yangListFieldToGoType takes a yang.Entry (listField) and returns a string corresponding to the Go
// type that should be used to represent it within its parent struct (the parent argument). A map, keyed
// by schema path, of the other code entities that have been extracted within the context that the
// listField is being generated are provided to the function as input, such that the struct representing
// the list itself can be cross-referenced.
//
// In all cases, the type of list field is the struct which is defined to reference the list, used as
// the base type. This type is then modified based on how the list is keyed:
//	- If the list is a config false, keyless list - a slice of the list's type is returned.
//	- If the list has a single key, a map, keyed by the single key's type is returned.
//	- If the list has multiple keys, a new struct is defined which represents the set of
//	  leaves that make up the key. The type of the list is then a map, keyed by the new struct
//	  type.
// In the case that the list has multiple keys, the type generated as the key of the list is returned.
// If errors are encountered during the type generation for the list, the error is returned.
func yangListFieldToGoType(listField *yang.Entry, listFieldName string, parent *Directory, goStructElements map[string]*Directory, gogen *goGenState) (string, *generatedGoMultiKeyListStruct, *generatedGoListMethod, error) {
	// The list itself, since it is a container, has a struct associated with it. Retrieve
	// this from the set of Directory structs for which code (a Go struct) will be
	//  generated such that additional details can be used in the code generation.
	listElem, ok := goStructElements[listField.Path()]
	if !ok {
		return "", nil, nil, fmt.Errorf("struct for %s did not exist", listField.Path())
	}

	// Find the name of the struct that refers to the list provided as input. The YANGCodeGenerator
	// instance stores this information. It is populated by structName which always runs prior to
	// this function being called (as all mappable entities, which includes lists, have been found.
	// Thus, in the case that this struct does not have a known name, then code cannot be generated
	// for it, and hence we skip the element.
	listName, ok := gogen.uniqueDirectoryNames[listField.Path()]
	if !ok {
		return "", nil, nil, fmt.Errorf("list element %s did not have a resolved name", listField.Path())
	}

	if listElem.ListAttr == nil || len(listElem.ListAttr.Keys) == 0 {
		// Keyless list therefore represent this as a slice of pointers to
		// the struct that represents the list element itself.
		return fmt.Sprintf("[]*%s", listName), nil, nil, nil
	}

	var listType string
	var multiListKey *generatedGoMultiKeyListStruct
	var listKeys []goStructField
	var listKeyStructName string

	// Key name elements are ordered per Section 7.8.2 of RFC6020. Rely on this
	// fact for determisitic ordering in output code and rendering.
	keyElemNames := strings.Fields(listField.Key)

	usedKeyElemNames := make(map[string]bool)
	for _, keName := range keyElemNames {
		keyField := goStructField{
			Name: genutil.MakeNameUnique(genutil.EntryCamelCaseName(listField.Dir[keName]), usedKeyElemNames),
			Type: listElem.ListAttr.Keys[keName].NativeType,
			Tags: fmt.Sprintf(`path:"%s"`, keName),
		}
		keyField.IsScalarField = IsScalarField(listField.Dir[keName], listElem.ListAttr.Keys[keName])
		listKeys = append(listKeys, keyField)
	}

	switch {
	case len(listElem.ListAttr.Keys) == 1:
		// This is a single keyed list, so we can represent it as a map with
		// a simple Go type as the key. Note that a leaf-list can never be
		// a key, so we do not need to handle the case whereby we would have to
		// have a slice which keys the list.
		listType = fmt.Sprintf("map[%s]*%s", listKeys[0].Type, listName)
	default:
		// This is a list with multiple keys, so we need to generate a new structure
		// that represents the list key itself - this struct is described in a
		// generatedGoMultiKeyListStruct struct, which is then expanded by a template to the struct
		// definition.
		listKeyStructName = fmt.Sprintf("%s_%s_Key", parent.Name, listFieldName)
		multiListKey = &generatedGoMultiKeyListStruct{
			KeyStructName: listKeyStructName,
			ParentPath:    util.SlicePathToString(parent.Path),
			ListName:      listFieldName,
			Keys:          listKeys,
		}
		listType = fmt.Sprintf("map[%s]*%s", listKeyStructName, listName)
	}

	// Generate the specification for the methods that should be generated for this
	// list, such that this can be handed to the relevant templates to generate code.
	listMethodSpec := &generatedGoListMethod{
		ListName:  listFieldName,
		ListType:  listName,
		KeyStruct: listKeyStructName,
		Keys:      listKeys,
		Receiver:  parent.Name,
	}

	return listType, multiListKey, listMethodSpec, nil
}

// writeGoEnum takes an input yangEnum, and generates the code corresponding
// to it. If the enum that is input has multiple enumerated types within it
// (i.e., is a union) then the relevant enumerated type is output for each
// included entity. If errors are encountered whilst mapping the enumeration to
// code, they are returned. The enumDefinition template is used to convert a
// constructed generatedGoEnumeration struct to code within the function.
func writeGoEnum(inputEnum *yangEnum) (goEnumCodeSnippet, error) {
	// initialised to be UNSET, such that it is possible to determine that the enumerated value
	// was not modified.
	values := map[int64]string{
		0: "UNSET",
	}

	// origValues stores the original set of value names, these are not maintained to be
	// Go-safe, and are rather used to map back to the original schema values if required.
	// 0 is not populated within this map, such that the values can be used to check whether
	// there was a valid entry in the original schema. The value is stored as a ygot
	// EnumDefinition, which stores the name, and in the case of identity values, the
	// module within which the identity was defined.
	origValues := map[int64]ygot.EnumDefinition{}

	switch {
	case inputEnum.entry.Type.IdentityBase != nil:
		// The inputEnum corresponds to an identityref - hence the values are defined
		// based on the values that the identity has. Since there is no explicit ordering
		// in an identity, then we go through and put the values in alphabetical order in
		// order to avoid reordering during code generation of the same entity.
		valNames := []string{}
		valLookup := map[string]*yang.Identity{}
		for _, v := range inputEnum.entry.Type.IdentityBase.Values {
			valNames = append(valNames, v.Name)
			valLookup[v.Name] = v
		}
		sort.Strings(valNames)

		for i, v := range valNames {
			values[int64(i)+1] = safeGoEnumeratedValueName(v)
			origValues[int64(i)+1] = ygot.EnumDefinition{
				Name:           v,
				DefiningModule: genutil.ParentModuleName(valLookup[v]),
			}
		}
	default:
		// The remaining enumerated types are all represented as an Enum type within the
		// Goyang entry construct. The values are accessed in a map keyed by an int64
		// and with a value of the name of the enumerated value - retrieved via ValueMap().
		for i, value := range inputEnum.entry.Type.Enum.ValueMap() {
			values[i+1] = safeGoEnumeratedValueName(value)
			origValues[i+1] = ygot.EnumDefinition{Name: value}
		}
	}

	// Initialise the input to the template, and generate the output.
	templateInput := generatedGoEnumeration{
		EnumerationPrefix: inputEnum.name,
		Values:            values,
	}

	var buf bytes.Buffer
	err := goEnumDefinitionTemplate.Execute(&buf, templateInput)
	return goEnumCodeSnippet{
		constDef:    buf.String(),
		valToString: origValues,
		name:        inputEnum.name,
	}, err
}

// findMapPaths takes an input field name for a parent Directory and calculates the set of schemapaths that it represents.
// If absolutePaths is set, the paths are absolute otherwise they are relative to the parent. If
// the input entry is a key to a list, and is of type leafref, then the corresponding target leaf's
// path is also returned.
// TODO(wenbli): This is used by both Go and proto generation, it should be moved to genstate.go or genutil.
func findMapPaths(parent *Directory, fieldName string, compressPaths, absolutePaths bool) ([][]string, error) {
	childPath, err := FindSchemaPath(parent, fieldName, absolutePaths)
	if err != nil {
		return nil, err
	}
	mapPaths := [][]string{childPath}
	if !compressPaths || parent.ListAttr == nil {
		return mapPaths, nil
	}

	field, ok := parent.Fields[fieldName]
	if !ok {
		return nil, fmt.Errorf("field name %s does not exist in Directory %s", fieldName, parent.Path)
	}
	fieldSlicePath := util.SchemaPathNoChoiceCase(field)

	// Handle specific issue of compressed path schemas, where a key of the
	// parent list is a leafref to this leaf.
	for _, k := range parent.ListAttr.KeyElems {
		// If the key element has the same path as this element, and the
		// corresponding element that is within the parent's container is of
		// type leafref, then within an OpenConfig schema this means that
		// the key leaf was a pointer to this leaf. To this end, we set
		// isKey to true so that the struct field can be mapped to the
		// leafref leaf within the schema as well as the target of the
		// leafref.
		if k.Parent == nil || k.Parent.Parent == nil || k.Parent.Parent.Dir[k.Name] == nil || k.Parent.Parent.Dir[k.Name].Type == nil {
			return nil, fmt.Errorf("invalid compressed schema, could not find the key %s or the grandparent of %s", k.Name, k.Path())
		}

		// If a key of the list is a leafref that points to the field,
		// then add this as an alternative path.
		// Note: if k is a leafref, buildListKey() would have already
		// resolved it the field that the leafref points to. So, we
		// compare their absolute paths for equality.
		if k.Parent.Parent.Dir[k.Name].Type.Kind == yang.Yleafref && cmp.Equal(util.SchemaPathNoChoiceCase(k), fieldSlicePath) {
			// The path of the key element is simply the name of the leaf under the
			// list, since the YANG specification enforces that keys are direct
			// children of the list.
			keyPath := []string{fieldSlicePath[len(fieldSlicePath)-1]}
			if absolutePaths {
				// If absolute paths are required, then the 'config' or 'state' container needs to be omitted from
				// the complete path for the secondary mapping.
				keyPath = append([]string{""}, fieldSlicePath[1:len(fieldSlicePath)-2]...)
				keyPath = append(keyPath, fieldSlicePath[len(fieldSlicePath)-1])
			}
			mapPaths = append(mapPaths, keyPath)
			break
		}
	}
	return mapPaths, nil
}

// generateEnumMap outputs a map from the enumMapTemplate. It takes an input of
// a map corresponding to the enumerated types that are defined in the input YANG
// schema, keyed by their generating Go name. The values of the map for each key is
// a map of a int64 value of the enum, and its string name as represented in the
// original YANG schema,
func generateEnumMap(enumValues map[string]map[int64]ygot.EnumDefinition) (string, error) {
	if len(enumValues) == 0 {
		return "", nil
	}

	var buf bytes.Buffer
	if err := goEnumMapTemplate.Execute(&buf, enumValues); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// generateEnumTypeMap outputs a map using the enumTypeMap template. It takes an
// input of a map, keyed by schema path, to the string names of the enumerated
// types that can correspond to the schema path. The map generated allows a
// schemapath to be mapped into the reflect.Type representing the enum value.
func generateEnumTypeMap(enumTypeMap map[string][]string) (string, error) {
	var buf bytes.Buffer
	if err := goEnumTypeMapTemplate.Execute(&buf, enumTypeMap); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// generateEnumTypeMapAccessor generates a function which returns the defined
// enumTypeMap for a struct.
func generateEnumTypeMapAccessor(b *bytes.Buffer, s generatedGoStruct) error {
	return goEnumTypeMapAccessTemplate.Execute(b, s)
}

// writeGoSchema generates Go code which serialises the rawSchema byte slice
// provided and stores it in a variable which can be written out to the generated
// Go code file.
func writeGoSchema(js []byte, schemaVarName string) (string, error) {
	jbyte, err := WriteGzippedByteSlice(js)
	if err != nil {
		return "", fmt.Errorf("could not write Byte slice: %v", err)
	}

	vn := defaultSchemaVarName
	if schemaVarName != "" {
		vn = schemaVarName
	}

	in := struct {
		VarName string
		Schema  []string
	}{
		VarName: vn,
		Schema:  BytesToGoByteSlice(jbyte),
	}

	var buf bytes.Buffer
	if err := schemaVarTemplate.Execute(&buf, in); err != nil {
		return "", err
	}

	return buf.String(), nil
}

// goLeafDefault returns the default value of the leaf e if specified. If it
// is unspecified, the value specified by the type is returned if it is not nil,
// otherwise nil is returned to indicate no default was specified.
func goLeafDefault(e *yang.Entry, t *MappedType) *string {
	if e.Default != "" {
		if t.IsEnumeratedValue {
			return enumDefaultValue(t.NativeType, e.Default, goEnumPrefix)
		}
		return quoteDefault(&e.Default, t.NativeType)
	}

	if t.DefaultValue != nil {
		return quoteDefault(t.DefaultValue, t.NativeType)
	}

	return nil
}

// quoteDefault adds quotation marks to the value string if the goType specified
// is a string, and hence requires quoting.
func quoteDefault(value *string, goType string) *string {
	if value == nil {
		return nil
	}

	if goType == "string" {
		return ygot.String(fmt.Sprintf("%q", *value))
	}

	return value
}
